/**
 * Copyright (c) 2012-2014, Yang Yang <cntototo@gmail.com>
 *
 * This software may be distributed under the terms of the New BSD License.
 * See README for more details.
 */

#include "os.h"

#include <sys/time.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <net/if_arp.h>
#include <unistd.h>

#include "linux_spec.h"
#include "include.h"
#include "log.h"
#include "util.h"
#include "def.h"

/***********************************************************
 *                    Local functions
 ***********************************************************
 */
int turn_iface(int sock, const char *ifname, int up)
{
	struct ifreq ifr;

	if (sock < 0)
		return FAIL;

	memset(&ifr, 0, sizeof(ifr));
	strncpy(ifr.ifr_name, ifname, IFNAMSIZ);

	if (ioctl(sock, SIOCGIFFLAGS, &ifr) != 0) {
		wfs_perror("ioctl SIOCGIFFLAGS");
		return FAIL;
	}

	if (up) {
		if (ifr.ifr_flags & IFF_UP)
			return SUCCESS;
		ifr.ifr_flags |= IFF_UP;
	} else {
		if (!(ifr.ifr_flags & IFF_UP))
			return SUCCESS;
		ifr.ifr_flags &= ~IFF_UP;
	}

	if (ioctl(sock, SIOCSIFFLAGS, &ifr) != 0) {
		wfs_perror("ioctl SIOCSIFFLAGS");
		return FAIL;
	}

	return SUCCESS;
}


/***********************************************************
 *                    Global functions
 ***********************************************************
 */
enum wfs_internal_err
os_add_time(struct wfs_timeval *a, struct wfs_timeval *b,
		struct wfs_timeval *sum)
{
	assert(a && b && sum);

	return os_add_time_val(a, b->sec, b->usec, sum);
}


enum wfs_internal_err
os_add_time_val(struct wfs_timeval *time, os_time_t sec, os_time_t usec,
		struct wfs_timeval *sum)
{
	assert(time && sum);

	if (sec > LONG_MAX - usec / 1000000) {
		wfs_printf(LOG_ERR, "Time to add is too large");
		return WFS_INTERNAL_ERR_INVAL;
	}

	sec += usec / 1000000;
	usec = usec % 1000000;

	if (time->sec > LONG_MAX - sec) {
		wfs_printf(LOG_ERR, "Time sum overflow");
		return WFS_INTERNAL_ERR_INVAL;
	}

	sum->sec = time->sec + sec;
	sum->usec = time->usec + usec;

	if (sum->sec > LONG_MAX - sum->usec / 1000000) {
		wfs_printf(LOG_ERR, "Time sum overflow");
		return WFS_INTERNAL_ERR_INVAL;
	}

	sum->sec += sum->usec / 1000000;
	sum->usec = sum->usec % 1000000;

	return WFS_INTERNAL_ERR_SUCCESS;
}


enum wfs_internal_err os_new_time(struct wfs_timeval **tv)
{
	assert(tv);

	*tv = (struct wfs_timeval *) zalloc(sizeof(**tv));
	if (!(*tv)) {
		wfs_perror("zalloc");
		return WFS_INTERNAL_ERR_OOM;
	}

	return WFS_INTERNAL_ERR_SUCCESS;
}


void os_free_time(struct wfs_timeval **tv)
{
	if (!tv)
		return;
	FREE(*tv);
}


enum wfs_internal_err os_get_time(struct wfs_timeval *tv)
{
	struct timeval t;

	assert(tv);

	memset(&t, 0, sizeof(t));
	if (gettimeofday(&t, NULL) < 0) {
		wfs_perror("gettimeofday");
		return WFS_INTERNAL_ERR_OS;
	}

	tv->sec = t.tv_sec;
	tv->usec = t.tv_usec;

	return WFS_INTERNAL_ERR_SUCCESS;
}


int os_is_time_before(struct wfs_timeval *before, struct wfs_timeval *after)
{
	assert(before && after);

	if (before->sec < after->sec || (before->sec == after->sec
			&& before->usec < after->usec))
		return TRUE;

	return FALSE;
}


int os_is_time_populated(struct wfs_timeval *tv)
{
	assert(tv);

	if (tv->sec != 0 || tv->usec != 0)
		return TRUE;

	return FALSE;
}


void os_sleep(os_time_t sec, os_time_t usec)
{
	if (sec)
		sleep(sec);
	if (usec)
		usleep(usec);
}


enum wfs_internal_err
os_sub_time(struct wfs_timeval *minuend, struct wfs_timeval *subtrahend,
		struct wfs_timeval *diff)
{
	assert(minuend && subtrahend && diff);

	if (os_is_time_before(minuend, subtrahend)) {
		wfs_printf(LOG_ERR, "Do not produce negative time difference");
		return WFS_INTERNAL_ERR_INVAL;
	}

	diff->sec = minuend->sec - subtrahend->sec;
	diff->usec = minuend->usec - subtrahend->usec;
	if (diff->usec < 0) {
		--diff->sec;
		diff->usec += 1000000;
	}

	return WFS_INTERNAL_ERR_SUCCESS;
}


enum wfs_internal_err
os_sub_time_val(struct wfs_timeval *time, os_time_t sec, os_time_t usec,
		struct wfs_timeval *diff)
{
	assert(time && diff);

	if (sec < 0 || usec < 0) {
		wfs_printf(LOG_ERR, "Do not subtract negative value");
		return WFS_INTERNAL_ERR_INVAL;
	}

	if (sec > LONG_MAX - usec / 1000000) {
		wfs_printf(LOG_ERR, "Time to subtract is too large");
		return WFS_INTERNAL_ERR_INVAL;
	}

	sec += usec / 1000000;
	usec = usec % 1000000;

	if (time->sec < sec || (time->sec == sec && time->usec < usec)) {
		wfs_printf(LOG_ERR, "Do not produce negative time difference");
		return WFS_INTERNAL_ERR_INVAL;
	}

	diff->sec = time->sec - sec;
	diff->usec = time->usec - usec;
	if (diff->usec < 0) {
		--diff->sec;
		diff->usec += 1000000;
	}

	return WFS_INTERNAL_ERR_SUCCESS;
}


enum wfs_internal_err
os_time_till_now(struct wfs_timeval *before, struct wfs_timeval *diff)
{
	enum wfs_internal_err ret = WFS_INTERNAL_ERR_SUCCESS;
	struct wfs_timeval now;

	assert(before && diff);

	memset(&now, 0, sizeof(now));

	ret = os_get_time(&now);
	if (ret)
		return ret;

	ret = os_sub_time(&now, before, diff);

	return ret;
}


int linux_is_iface_up(int sock, const char *ifname)
{
	struct ifreq ifr;

	if (sock < 0)
		return FALSE;

	memset(&ifr, 0, sizeof(ifr));
	strncpy(ifr.ifr_name, ifname, IFNAMSIZ);

	if (ioctl(sock, SIOCGIFFLAGS, &ifr) != 0) {
		wfs_perror("ioctl SIOCGIFFLAGS");
		return FALSE;
	}

	return (ifr.ifr_flags & IFF_UP) ? TRUE : FALSE;
}


enum wfs_internal_err linux_get_hwaddr(int sock, const char *ifname, u8 *addr)
{
	struct ifreq ifr;

	if (sock < 0)
		return WFS_INTERNAL_ERR_INVAL;

	memset(&ifr, 0, sizeof(ifr));
	strncpy(ifr.ifr_name, ifname, IFNAMSIZ);

	if (ioctl(sock, SIOCGIFHWADDR, &ifr) != 0) {
		wfs_perror("ioctl SIOCGIFHWADDR");
		return WFS_INTERNAL_ERR_OS;
	}

	if (ifr.ifr_hwaddr.sa_family != ARPHRD_ETHER) {
		wfs_printf(LOG_ERR, "%s: Invalid HW addr family 0x%04x",
				ifname, ifr.ifr_hwaddr.sa_family);
		return WFS_INTERNAL_ERR_OS;
	}

	memcpy(addr, ifr.ifr_hwaddr.sa_data, ETH_ALEN);

	return WFS_INTERNAL_ERR_SUCCESS;
}


enum wfs_internal_err
linux_set_hwaddr(int sock, const char *ifname, const u8 *addr)
{
	struct ifreq ifr;

	if (sock < 0)
		return WFS_INTERNAL_ERR_INVAL;

	memset(&ifr, 0, sizeof(ifr));
	strncpy(ifr.ifr_name, ifname, IFNAMSIZ);
	memcpy(ifr.ifr_hwaddr.sa_data, addr, ETH_ALEN);
	ifr.ifr_hwaddr.sa_family = ARPHRD_ETHER;

	if (ioctl(sock, SIOCSIFHWADDR, &ifr) != 0) {
		wfs_perror("ioctl SIOCSIFHWADDR");
		return WFS_INTERNAL_ERR_OS;
	}

	return WFS_INTERNAL_ERR_SUCCESS;
}


enum wfs_internal_err linux_turn_iface_down(int sock, const char *ifname)
{
	return turn_iface(sock, ifname, 0);
}


enum wfs_internal_err linux_turn_iface_up(int sock, const char *ifname)
{
	return turn_iface(sock, ifname, 1);
}
